﻿using System;
using System.Collections.Generic;
using System.Threading;

namespace GiveGraphMappers.ContextBalancer
{
    public class MonitorBoundedQueue<T> 
    {
        public class QueueClosedException : Exception { }

        private enum BoundedQueueState { Open, Closing, Closed }

        private class WaitElement
        {
            public T Elem;
            public bool OperationCompleted;
        }
        private readonly LinkedList<T> _elems = new LinkedList<T>();
                
        private readonly LinkedList<WaitElement> _waitingTasks = new LinkedList<WaitElement>();
      

        private readonly int _capacity;
        private volatile BoundedQueueState _state = BoundedQueueState.Open;



        public MonitorBoundedQueue(int capacity)
        {
            _capacity = capacity;
        }


        private void RemoveAndWaitAllTasksFromList(LinkedList<WaitElement> elems)
        {
            do
            {
                var elem = elems.Last.Value;
                elems.RemoveLast();
                SyncUtils.Notify(this,elem);
            } while (elems.Count != 0);
        }


        

        public T Get(int timeout)
        {
         
            if (IsClosed)
                throw new QueueClosedException();

            lock (this)
            {
                if (IsClosed)
                    throw new QueueClosedException();

                if (_elems.Count != 0)
                {

                    var myElem = _elems.Last.Value;
                    _elems.RemoveLast();

                    if (IsClosing && _elems.Count == 0)
                    { 
                        _state = BoundedQueueState.Closed;
                        RemoveAndWaitAllTasksFromList(_waitingTasks); //Putters
                    }

                    if (_elems.Count == (_capacity-1) && _waitingTasks.Count != 0)
                    {
                        var putterTask = _waitingTasks.Last.Value;
                        _waitingTasks.RemoveLast();
                        _elems.AddFirst(putterTask.Elem);
                        putterTask.OperationCompleted = true;
                        SyncUtils.Notify(this, putterTask);
                    }

                   
                    
                    return myElem;

                }

                if (IsClosing || IsClosed)
                    throw new QueueClosedException();

                var waitElement = new WaitElement();
                var interrupted = false;
                _waitingTasks.AddFirst(waitElement);
                Exception exception = null;
                int referenceTime = Environment.TickCount;
                do
                {
                    try
                    {
                        SyncUtils.Wait(this, waitElement, timeout);
                    }
                    catch (ThreadInterruptedException tie)
                    {
                        if (!waitElement.OperationCompleted)
                            exception = tie;
                        else
                            interrupted = true;
                    }

                    if (waitElement.OperationCompleted)
                    {
                        if(interrupted)
                            Thread.CurrentThread.Interrupt();
                        return waitElement.Elem;
                    }

                    if (_state == BoundedQueueState.Closed && exception == null)
                    {
                        exception = new QueueClosedException();
                    }

                    if (SyncUtils.AdjustTimeout(ref referenceTime, ref timeout) == 0 && exception == null)
                    {
                        exception = new TimeoutException();
                    }

                    if (exception != null)
                    {
                        _waitingTasks.Remove(waitElement);

                        if (interrupted)
                            Thread.CurrentThread.Interrupt();
                        
                        throw exception;
                    }

                } while (true);
            }
        }

        public bool Put(T t, int timeout)
        {
            if (IsClosing || IsClosed)
                throw new QueueClosedException();

            lock (this)
            {

                if (IsClosing || IsClosed)
                    throw new QueueClosedException();

                if (_elems.Count < _capacity)
                {

                    if (_elems.Count == 0 && _waitingTasks.Count != 0)
                    {
                        var waitingTask = _waitingTasks.Last.Value; //getter
                        _waitingTasks.RemoveLast();
                        waitingTask.Elem = t;
                        waitingTask.OperationCompleted = true;
                        SyncUtils.Notify(this, waitingTask);
                    }
                    else
                        _elems.AddFirst(t);

                    return true;
                }

                var waitElement = new WaitElement() {Elem = t};
                _waitingTasks.AddFirst(waitElement);
                var timeRef = Environment.TickCount;
                var interrupted = false;
                do
                {
                    try
                    {
                        SyncUtils.Wait(this, waitElement);
                    }
                    catch (ThreadInterruptedException)
                    {
                        
                            interrupted = true;
                       
                       
                    }

                    if (waitElement.OperationCompleted)
                    {
                        if (interrupted)
                            Thread.CurrentThread.Interrupt();
                        return true;
                    }

                    if (SyncUtils.AdjustTimeout(ref timeRef, ref timeout) == 0 || interrupted || IsClosing || IsClosed)
                    {
                        _waitingTasks.Remove(waitElement);
                        if(interrupted)
                            Thread.CurrentThread.Interrupt();
                        return false;
                    }
                } while (true);
            }
        }

        public void Close()
        {
            lock (this)
            { 
                
                if (_elems.Count == 0)
                {
                   
                    //
                    //  There are no elements to return just close the queue
                    //
                    _state = BoundedQueueState.Closed;
                    //
                    //  If There are getters waiting, wake them up
                    //
                    if(_waitingTasks.Count != 0)
                        RemoveAndWaitAllTasksFromList(_waitingTasks);

                }

                else if (_elems.Count == _capacity && _waitingTasks.Count != 0)
                {
                    //
                    //  There are putters waiting, wake them up
                    //
                    RemoveAndWaitAllTasksFromList(_waitingTasks);

                }
                //
                //  set the queue state to closing so that getters tha retrieve elements from the queue
                //
                _state = BoundedQueueState.Closing;
            }
        }

        public bool IsClosing
        {
            get { return _state == BoundedQueueState.Closing; }
        }

        public bool IsClosed
        {
            get { return _state == BoundedQueueState.Closed; }
        }
    }
}
